#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Threading.Tasks;
using Autofac.Features.OwnedInstances;
using BitwiseOre.SharedMemory.Core;
using BitwiseOre.SharedMemory.Core.Common;
using NUnit.Framework;
using Moq;

namespace BitwiseOre.Tests.SharedMemory.Connection
{
    [TestFixture]
    public sealed class ConnectorFixture : AssertionHelper
    {
        [Test(Description = "Checks that beginning a connect operation creates a new guild.")]
        public void Connect_CreatesNewGuid()
        {
            var guidcreated = false;
            var factory = CreateFactory(guidFactory: () =>
            {
                guidcreated = true;
                return Guid.NewGuid();
            });

            factory.Connect(TimeSpan.FromSeconds(10), g => g);

            Expect(guidcreated, "No new guid was created while beginning to connect.");
        }

        [Test(Description = "Checks that beginning a connect operation create a new event wait handle.")]
        public void Connect_CreatesNewEvent()
        {
            var eventcreated = false;
            var factory = CreateFactory(eventFactory: s =>
            {
                eventcreated = true;
                return MockEventFactory(s);
            });

            factory.Connect(TimeSpan.FromSeconds(10), g => g);

            Expect(eventcreated, "No event was created while beginning to connect.");
        }

        [Test(Description = "Checks that beginning a connect operation uses the created guid as the name of the created event.")]
        public void Connect_UsesGuidToCreateNewEvent()
        {
            var guid = Guid.NewGuid();
            var eventname = string.Empty;
            var factory = CreateFactory(eventFactory: name =>
            {
                eventname = name;
                return MockEventFactory(name);
            },
                                        guidFactory: () => guid);

            factory.Connect(TimeSpan.FromSeconds(10), g => g);

            Expect(eventname, EqualTo(guid.ToString()),
                "Unexpected event name used when beginning a connect operation.");
        }

        [Test(Description = "Checks the beginning a connect operation enques to the underlying listner queue.")]
        public void Connect_EnqueuesToListnerQueue()
        {
            var queue = new Mock<IListenerQueue>();
            var factory = CreateFactory(queue.Object);

            factory.Connect(TimeSpan.FromSeconds(10), g => g);

            queue.Verify(q => q.Enqueue(It.IsAny<Guid>()),
                "Nothing enqueued to the listner queue.");
        }

        [Test(Description = "Checks that beginning a connect operation enqueues the newly create guid")]
        public void Connect_EnqueuesCreatedGuid()
        {
            var guid = Guid.NewGuid();
            var queue = new Mock<IListenerQueue>();
            var factory = CreateFactory(queue.Object, guidFactory: () => guid);

            factory.Connect(TimeSpan.FromSeconds(10), g => g);

            queue.Verify(q => q.Enqueue(It.Is<Guid>(g => g == guid)),
                "Expected guid not enqueued to the listner queue.");
        }

        [Test(Description = "Checks that beginning a connect operation waits on the the event that is created.")]
        public void Connect_WaitsOnCreatedEvent()
        {
            var eventhandle = new Mock<IEventWaitHandle>();
            eventhandle.Setup(e => e.Wait(It.IsAny<TimeSpan>()))
                .Returns(Task.Factory.StartNew(() => { }));
            var ownedevent = new Owned<IEventWaitHandle>(eventhandle.Object, new Mock<IDisposable>().Object);
            var factory = CreateFactory(eventFactory: _ => ownedevent);

            factory.Connect(TimeSpan.FromSeconds(10), g => g);

            eventhandle.Verify(e => e.Wait(It.IsAny<TimeSpan>()),
                "Wait not called on the created event.");
        }

        [Test(Description = "Checks that waiting on the event uses the connection timeout.")]
        public void Connect_WaitsWithConnectionTimeout()
        {
            var eventhandle = new Mock<IEventWaitHandle>();
            eventhandle.Setup(e => e.Wait(It.IsAny<TimeSpan>()))
                .Returns(Task.Factory.StartNew(() => { }));
            var ownedevent = new Owned<IEventWaitHandle>(eventhandle.Object, new Mock<IDisposable>().Object);
            var factory = CreateFactory(eventFactory: _ => ownedevent);
            var timeout = TimeSpan.FromMinutes(5);

            factory.Connect(timeout, g => g);

            eventhandle.Verify(
                e =>
                e.Wait(It.Is<TimeSpan>(ts => ts == timeout)),
                       "Wait not called on the created event with the expected parameters.");
        }

        [Test(Description = "Checks that the owned event is diposed after the wait on it completes.")]
        public void Connect_DisposesOwnedEventOnWaitCompletions()
        {
            var eventhandle = new Mock<IEventWaitHandle>();
            var source = new TaskCompletionSource<object>();
            eventhandle.Setup(e => e.Wait(It.IsAny<TimeSpan>())).Returns(source.Task);
            var disposer = new Mock<IDisposable>();
            var ownedevent = new Owned<IEventWaitHandle>(eventhandle.Object, disposer.Object);
            var factory = CreateFactory(eventFactory: _ => ownedevent);

            factory.Connect(TimeSpan.FromSeconds(10), g => g);
            source.SetResult(null);

            disposer.Verify(d => d.Dispose(), "The owned event was not disposed after completing the wait.");
        }

        [Test(Description = "Checks that completing the connection operation uses the supplied factory to create the result")]
        public void Connect_UsesFactoryToCreateResult()
        {
            var eventhandle = new Mock<IEventWaitHandle>();
            var source = new TaskCompletionSource<object>();
            eventhandle.Setup(e => e.Wait(It.IsAny<TimeSpan>())).Returns(source.Task);
            var ownedevent = new Owned<IEventWaitHandle>(eventhandle.Object, new Mock<IDisposable>().Object);
            var factory = CreateFactory(eventFactory: _ => ownedevent);
            var expectedresult = new object();

            var task = factory.Connect(TimeSpan.FromSeconds(10), _ => expectedresult);
            source.SetResult(null);
            task.Wait();

            Expect(task.Result, EqualTo(expectedresult),
                "Unexpected result obtained from completing the connection operation.");
        }


        private static Connector CreateFactory(
            IListenerQueue queue = null,
            Func<string, Owned<IEventWaitHandle>> eventFactory = null,
            Func<Guid> guidFactory = null)
        {
            queue = queue ?? new Mock<IListenerQueue>().Object;
            eventFactory = eventFactory ?? MockEventFactory;
            var factory = new Mock<IEventWaitHandleFactory>();
            factory.Setup(f => f.Create(It.IsAny<string>())).Returns(eventFactory);
            guidFactory = guidFactory ?? Guid.NewGuid;

            return new Connector(queue, factory.Object, guidFactory);
        }

        private static Owned<IEventWaitHandle> MockEventFactory(string name)
        {
            var eventhandle = new Mock<IEventWaitHandle>();
            eventhandle.Setup(e => e.Wait(It.IsAny<TimeSpan>()))
                .Returns(Task.Factory.StartNew(() => { }));
            return new Owned<IEventWaitHandle>(eventhandle.Object, new Mock<IDisposable>().Object);
        }
    }
}